package support.limit.service.impl;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.DependsOn;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;
import support.limit.enums.RateLimitConstantEnums;
import support.limit.model.LimitDTO;
import support.limit.ratelimit.AbstractRateLimiter;
import support.limit.service.RateLimitAlgorihm;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

/**
 *  滑动窗口算法
 * @Description  SlidingWindowAlgorithmImpl
 * @Author guoyb
 * @Date   2023/12/22 15:36
 */
@Service
// @DependsOn注解主要用于指定当前Bean对象所依赖的其他Bean对象。Spring在创建当前Bean之前，会先创建由@DependsOn注解指定的依赖Bean
// @DependsOn("rateLimiter")
@Slf4j
public class SlidingWindowAlgorithmImpl implements RateLimitAlgorihm {

    @Resource
    RedisTemplate redisTemplate;

    @Override
    public void handler(LimitDTO limitDTO) {

        // 在 {time} 秒内仅允许访问 {count} 次。
        Long time = limitDTO.getRefreshInterval();
        Long count = limitDTO.getLimit();
        // 根据用户IP（可选）和接口方法，构造key
        String combineKey = "getkey()";

        // 限流逻辑实现
        ZSetOperations zSetOperations = redisTemplate.opsForZSet();
        // 记录本次访问的时间结点
        long currentMs = System.currentTimeMillis();
        zSetOperations.add(combineKey, currentMs, currentMs);
        // 这一步是为了防止member一直存在于内存中
        redisTemplate.expire(combineKey, time, TimeUnit.SECONDS);
        // 移除{time}秒之前的访问记录（滑动窗口思想）
        zSetOperations.removeRangeByScore(combineKey, 0, currentMs - time * 1000);

        // 获得当前窗口内的访问记录数
        Long currCount = zSetOperations.zCard(combineKey);
        // 限流判断
        if (currCount > count) {
            log.error("[limit] 限制请求数'{}',当前请求数'{}',缓存key'{}'", count, currCount, combineKey);
            throw new RuntimeException("访问过于频繁，请稍后再试!");
        }
    }
}
